.Z80
.COMMENT @

***************************************************************
*							      *
*	---   Custom BIOS for CP/M 2.2   ---		      *
*							      *
* Version: 1.0					28 0ct 1984.  *
*							      *
* by						Mark Little.  *
*							      *
***************************************************************

	Custom BIOS for the Ferguson Big Board I to allow
auto-execution of commands on Cold Boot ONLY.

NOTE:
-----
	This has been implemented for a specific system which
uses the parallel port for a Terminal. It also does not have
a printer implemented. You will most likely have to modify
the I/O jumps into the monitor and insert the printer setup in
the Cold Boot routine. The listing of the CBIOS supplied with
the system will show you how to do it. Also, the CBIOS supplied
with the original system modifies the monitor code, preventing
easy monitor modification. This version has eliminated that
problem (without affecting operation).
	The CBIOS uses a portion of the Monitor Work Area to
store its variables and implement its stack. Normally the area
used (0FF7CH - 0FFFFH) is unused once the monitor loses control
and is free for use, so BEWARE if you have software that uses
this area also.

INSTALLATION.
-------------

(1)	Modify to suit your system.
(2)	Assemble to produce a .HEX file
(3)	DDT CPM60.COM (supplied on original CP/M diskette)
(4)	ICBIOS.HEX (Identify File to use)
(5)	R1E7D (Read in file with offset 1F80H-103H)
(6)	^C (Exit DDT with changing memory image)
(7)	SYSGEN (RETURN with asked for Source Drive)
(8)	Test your modifications.
(9)	Place a copy of you modifications in Public Domain


							      @


MEMORY_SIZE	EQU	64		; 64k bytes of RAM
MONITOR_SIZE	EQU	4		;  4k bytes of Monitor
USER_MEMORY	EQU	MEMORY_SIZE-MONITOR_SIZE
MONITOR		EQU	USER_MEMORY*1024
BIAS		EQU	(USER_MEMORY-20)*1024
CCP		EQU	BIAS+03400H
CBIOS		EQU	CCP+01600H
BDOS		EQU	CCP+00806H	; BDOS Entry point
STACK		EQU	00000H		; Stack pointer
IOBYTE		EQU	00003H		; I/O Byte
CONSOLE_STATUS	EQU	MONITOR+6
CONSOLE_INPUT	EQU	MONITOR+9
LIST_OUTPUT	EQU	MONITOR+15
READ_DEVICE	EQU	MONITOR+21	; Read device
TIKCNT		EQU	0FF5DH		; Tick counter
RUN_UNIT	EQU	0FF79H		; Run unit
DISK_UNIT	EQU	RUN_UNIT+1	; Disk Unit
TRACK		EQU	DISK_UNIT+1	; Track
SECTOR		EQU	TRACK+1		; Sector
DMA_ADDRESS	EQU	SECTOR+1	; DMA address store
RETRY_COUNT	EQU	DMA_ADDRESS+2	; Retry count

SYDAP		EQU	28		; System Control Port
MAX_DRIVE	EQU	04H		; Max No of drives

CR		EQU	0DH		; Carriage return
LF		EQU	0AH		; Line feed
FF		EQU	0CH		; Form Feed
TAB		EQU	08H		; TAB character

;	Set assembly address.

	.PHASE	CBIOS

;	Jump Table into CBIOS.

	JP	COLD_BOOT		; Cold Boot Entry
	JP	WARM_BOOT		; Warm Boot Entry
	JP	CONSOLE_STATUS		; Console Status
	JP	CONSOLE_INPUT		; Console Input
	JP	CONSOLE_OUTPUT		; Console output
	JP	LIST_OUTPUT		; List output
	JP	PUNCH_DEVICE		; Punch output
	JP	READ_DEVICE		; Reader input
	JP	HOME_HEAD		; Home Disk head
	JP	SELECT_DRIVE		; Select the drive
	JP	SEEK_TRACK		; Seek to a sector
	JP	SET_SECTOR		; Set the sector
	JP	SET_DMA_ADDRESS		; Set DMA address
	JP	READ_SECTOR		; Read a sector
	JP	WRITE_SECTOR		; Write a sector
	JP	LIST_STATUS		; List status
	JP	TRANSLATE_SECTOR	; Locate physical

;	Send a character to the Punch Device.

PUNCH_DEVICE:
	LD	A,C
	JP	MONITOR+24		; Go to the monitor

;	Return permanent ready in this version.

LIST_STATUS:
	XOR	A			; Zero register
	DEC	A			; Reset the 'Z' flag
	RET

;	Console Output routine.

CONSOLE_OUTPUT:
	LD	A,C
	JP	MONITOR+12		; Send to Console

;	Cold Boot CBIOS Routine.

COLD_BOOT:
	XOR	A			; Zero the byte
	LD	(IOBYTE),A		; Reset I/O byte
	LD	HL,TITLE_MESSAGE	; Title
	CALL	SHOW_MESSAGE		; Send to console
	CALL	SET_DEFAULTS		; Default addresses
	JP	CCP			; Cold start the CCP

;	Warm Boot CBIOS Routine.

WARM_BOOT:
	LD	SP,STACK		; Monitor stack area
	LD	A,(DISK_UNIT)		; Get disk unit
	LD	(RUN_UNIT),A		; Save it
	LD	C,0			; Select disk 'A'
	CALL	SELECT_DRIVE		; Select it
	CALL	HOME_HEAD		; Send to track 0
	LD	HL,CCP			; Point to the CCP
	LD	BC,00D02H
	CALL	READ_LOOP		; Even sectors, track 0
	LD	HL,CCP+00080H		; Update DMA address
	LD	BC,00C03H
	CALL	READ_LOOP		; Odd sectors, track 1
	LD	C,1
	CALL	SEEK_TRACK		; Seek track 1
	JR	NZ,FATAL_LOAD_ERROR	; Oops - Abort it now

	LD	HL,BIAS+4080H
	LD	BC,00A01H
	CALL	READ_LOOP		; Odd sectors, track 1
	LD	HL,BIAS+04100H
	LD	BC,00902H
	CALL	READ_LOOP		; Even sectors, track 1
	CALL	SET_DEFAULTS		; Set default values
	JP	CCP+3			; Warm start CCP

;	Set up Default Values.

SET_DEFAULTS:
	LD	A,0C3H			; Jump command
	LD	(00000H),A		; Warm start jump
	LD	(00005H),A		; BDOS jump
	LD	(00038H),A		; Restart FF jump
	LD	HL,CBIOS+3		; Routine address
	LD	(00001H),HL		; Put into position
	LD	HL,BDOS			; Routine address
	LD	(00006H),HL		; Put into position
	LD	HL,MONITOR		; Monitor cold start
	LD	(00039H),HL		; Put into position
	LD	BC,00080H		; Default DMA address
	CALL	SET_DMA_ADDRESS		; Save it
	LD	A,(RUN_UNIT)
	LD	(DISK_UNIT),A
	LD	C,A			; Put unit into 'C'
	RET

;	Read a series of sectors.

READ_LOOP:
	LD	(DMA_ADDRESS),HL	; Save DMA address
	LD	A,C			; Get the sector
	LD	(SECTOR),A		; Save it
	PUSH	HL
	PUSH	BC			; Save registers
	CALL	READ_SECTOR		; Read a sector
	POP	BC
	POP	HL			; Restore 'em
	JR	NZ,FATAL_LOAD_ERROR	; Oops - Abort now

	INC	H			; Set DMA by 256 bytes
	INC	C
	INC	C			; Bump sector by 2
	DJNZ	READ_LOOP		; Read next sector
	RET

;	Show that CP/M was unable to be loaded from Disk.

FATAL_LOAD_ERROR:
	LD	HL,FATAL_ERROR		; Error message
	CALL	SHOW_MESSAGE		; Tell the user
	IN	A,(SYDAP)		; Get port status
	OR	01000100B		; Mask to kill disks
	OUT	(SYDAP),A		; Send to the port
	HALT				; Stop the processor

;	Store the Sector number passed.

SET_SECTOR:
	LD	A,C			; Get the sector
	LD	(SECTOR),A		; Save the Sector
	RET

;	Translate to the Physical Sector.

TRANSLATE_SECTOR:
	EX	DE,HL			; Swap to use
	ADD	HL,BC			; Point to sector
	LD	L,(HL)			; Put into L
	LD	H,0			; Make HL the sector
	RET

;	Set the DMA address.

SET_DMA_ADDRESS:
	LD	(DMA_ADDRESS),BC	; Save it
	RET

;	Select the Drive to use.

SELECT_DRIVE:
	LD	HL,0			; Clear result
	LD	A,C			; Get drive Number
	CP	MAX_DRIVE		; Out of bounds?
	RET	NC			; Yes - Abort

	LD	(DISK_UNIT),A		; Store new drive
	LD	L,A			; Drive into HL
	ADD	HL,HL			; Drive*2
	ADD	HL,HL			; Drive*4
	ADD	HL,HL			; Drive*8
	ADD	HL,HL			; Drive*16
	LD	DE,DRIVE_PARAM_HEADER	; Parameter block
	ADD	HL,DE			; Point to correct one
	PUSH	HL			; Save table pointer
	LD	B,0			; Fastest seek speed
	LD	C,A			; Load Disk Drive No.
	CALL	MONITOR+27		; Do monitor routine
	POP	HL			; Restore table pointer
	RET	Z			; Return if successful

	LD	C,1
	CALL	REPORT
	JR	NZ,SELECT_DRIVE2	; Jump if abort found

	LD	A,(DISK_UNIT)		; Else try again
	LD	C,A
	JR	SELECT_DRIVE

SELECT_DRIVE2:
	LD	HL,0			; Flag as bad one
	LD	A,(RUN_UNIT)
	LD	(DISK_UNIT),A		; Restore old drive
	RET

;	Home the Disk Drive's head to Track 0.

HOME_HEAD:
	CALL	MONITOR+30		; Monitor Home routine
	RET	Z			; Return if OK

	LD	C,2
	CALL	REPORT
	JR	Z,HOME_HEAD		; Retry if error

	RET

;	Seek to the selected track.

SEEK_TRACK:
	LD	A,C			; Get the track
	LD	(TRACK),A		; Save the track
SEEK_TRACK1:
	CALL	MONITOR+33		; Call the monitor
	RET	Z			; Return if no error

	LD	C,2			; Type of error
	CALL	REPORT			; Show the error
	RET	NZ			; Fatal Error - Abort!

	LD	A,(TRACK)		; Get the track
	LD	C,A
	JR	SEEK_TRACK1		; Retry


;	Read a Sector.

READ_SECTOR:
	LD	HL,(DMA_ADDRESS)	; Get the DMA address
	LD	A,(SECTOR)		; Get the sector
	LD	C,A
	CALL	MONITOR+36		; Call the monitor
	RET	Z			; Return if no error

	LD	C,3			; Type of error
	CALL	REPORT			; Show the error
	JR	Z,READ_SECTOR		; Retry

	RET				; Abort


;	Write a sector.

WRITE_SECTOR:
	LD	HL,(DMA_ADDRESS)	; Get the DMA address
	LD	A,(SECTOR)		; Get the sector
	LD	C,A
	CALL	MONITOR+39		; Call the monitor
	RET	Z			; Return if no error

	LD	C,4			; Type of error
	CALL	REPORT			; Show the error
	JR	Z,WRITE_SECTOR		; Retry

	RET				; Abort

;	Report on the error.

REPORT:
	LD	E,A			; Save error flags
	LD	D,C			; Save error type
	LD	HL,DISK_MESSAGES	; Point to messages
	CALL	SHOW_MESSAGE		; Show the message
	DEC	HL			; Step back
	LD	B,D			; Get error class
FIND_MESSAGE:
	CALL	SKIP			; Find next '$'
	DJNZ	FIND_MESSAGE		; Move to the message

	CALL	SHOW_MESSAGE		; Show error message
	LD	HL,ERROR_MESSAGE	; Finish error message
	CALL	SHOW_MESSAGE		; Show it

;	Determine the nature of the error.

	LD	A,E			; Get the flags
	RLA				; Test if drive unready
	JR	C,DISK_NOT_READY	; Yes - Skip this bit

	LD	E,A			; Save the flags
	LD	HL,RW_ERRORS		; R/W error messages
	LD	A,D			; Get the error type
	CP	3			; Sel/Seek or R/W?
	JR	NC,NOT_SEEK_ERRORS	; Read/Write errors

	LD	HL,SEEK_ERRORS		; Select message set
NOT_SEEK_ERRORS:
	LD	B,5			; Five bits to test
	RES	0,D			; Reset comma flag
WHICH_ERROR:
	SLA	E			; Move it left
	JR	NC,SELECT_MESSAGE	; Not this one!

	LD	A,','			; Load with comma
	BIT	0,D			; Print a comma?
	CALL	NZ,MONITOR+12		; Yes - Do it

	CALL	SHOW_MESSAGE		; Show the error msg
	SET	0,D			; Make a comma show
	JR	TEST_NEXT_BIT		; Continue

SELECT_MESSAGE:
	CALL	SKIP			; Move to next message
	RES	0,D			; Kill comma flag
TEST_NEXT_BIT:
	DJNZ	WHICH_ERROR		; Test all bits

	LD	HL,TRK_SEC_MESSAGE	; Track/Sector message
	CALL	SHOW_MESSAGE		; Display it
	LD	A,(TRACK)		; Get the track number
	CALL	SHOW_HEX_BYTE		; Show it
	LD	A,'/'			; Delimiter
	CALL	MONITOR+12		; Send to console
	LD	A,(SECTOR)		; get the sector number
	CALL	SHOW_HEX_BYTE		; Show it
PERM_ERROR:
	LD	A,1			; Flag permanent error
	OR	A
	RET

DISK_NOT_READY:
	LD	HL,DISK_UNREADY		; Show message
	CALL	SHOW_MESSAGE		; Show 'em
	CALL	CONSOLE_STATUS		; Key Pressed?
	JR	Z,WAIT_A_SECOND		; No - Wait, then retry

	CALL	CONSOLE_INPUT		; Get the character
	CP	'C'-40H			; Abort it?
	JR	Z,PERM_ERROR		; Yes!

WAIT_A_SECOND:
	LD	A,(TIKCNT)		; Get the current tick
	LD	B,A			; Save it
WAIT:
	LD	A,(TIKCNT)		; Get the time
	CP	B			; Same?
	JR	Z,WAIT			; Yes - Wait

	XOR	A			; Flag a retry
	RET

;	Skip a message string.

SKIP:
	PUSH	BC			; Save
	LD	B,255			; Arbitary large No.
	LD	A,'$'			; Terminator
	CPIR				; Block search
	POP	BC			; Restore
	RET

;	Send a message string to the Console.

SHOW_MESSAGE:
	LD	A,(HL)			; get a character
	CP	'$'			; EOT?
	INC	HL			; Step pointer
	RET	Z			; Yes - Exit

	CALL	MONITOR+12		; Send to console
	JR	SHOW_MESSAGE		; Continue

;	Send a byte to the screen in HEX format.

SHOW_HEX_BYTE:
	PUSH	AF			; Save byte
	RRA
	RRA
	RRA
	RRA				; Swap nibbles
	CALL	SHOW_HEX_NIBBLE		; Show it
	POP	AF			; Restore byte
SHOW_HEX_NIBBLE:
	AND	00001111B		; Select lower nibble
	ADD	A,90H
	DAA
	ADC	A,40H
	DAA				; Convert to hex
	JP	MONITOR+12		; Send to console

;	Title of the CBIOS.

TITLE_MESSAGE:
	DEFB	FF,CR,LF
	DEFM	"Iceworks' CP/M 2.2 Version: 1.0"
	DEFB	CR,LF,'$'

;	Fatal Load Error Message.

FATAL_ERROR:
	DEFB	FF,TAB
	DEFM	"UNABLE TO LOAD CP/M"
	DEFB	CR,LF,'$'

;	Disk Error Messages.

DISK_MESSAGES:
	DEFB	CR,LF
	DEFM	'BIOS $'
	DEFM	'SELECT $'
	DEFM	'SEEK $'
	DEFM	'READ $'
	DEFM	'WRITE $'

;	Tail of Error Message.

ERROR_MESSAGE:
	DEFM	'ERROR $'

;	Seek Error Messages.

SEEK_ERRORS:
	DEFM	'$'
	DEFM	'$'
	DEFM	'CANNOT SEEK$'
	DEFM	'BAD CRC$'
	DEFM	'CANNOT RESTORE$'

;	Drive Not Ready Message.

DISK_UNREADY:
	DEFB	CR,LF
	DEFM	'Drive not ready -'
	DEFM	CR,LF,'$'

;	Read/Write Errors.

RW_ERRORS:
	DEFM	'WRITE PROTECTED$'
	DEFM	'WRITE FAULT$'
	DEFM	'RECORD NOT FOUND$'
	DEFM	'BAD CRC$'
	DEFM	'DATA OVERRUN$'

;	Track/Sector Message.

TRK_SEC_MESSAGE:
	DEFM	' Track/Sector = $'

;	Drive Parameter Headers for a 4 drive system.

DRIVE_PARAM_HEADER:

	DEFW	SECTAB,00000H		; Disk Unit 0
	DEFW	00000H,00000H
	DEFW	DIRBUF,DPBLOK
	DEFW	CHECK0,ALLOC0

	DEFW	SECTAB,00000H		; Disk Unit 1
	DEFW	00000H,00000H
	DEFW	DIRBUF,DPBLOK
	DEFW	CHECK1,ALLOC1

	DEFW	SECTAB,00000H		; Disk Unit 2
	DEFW	00000H,00000H
	DEFW	DIRBUF,DPBLOK
	DEFW	CHECK2,ALLOC2

	DEFW	SECTAB,00000H		; Disk Unit 3
	DEFW	00000H,00000H
	DEFW	DIRBUF,DPBLOK
	DEFW	CHECK3,ALLOC3

;	Sector Translation Table (Standard 1 in 6 interleave).

SECTAB:
	DEFB	1,7,13,19
	DEFB	25,5,11,17
	DEFB	23,3,9,15
	DEFB	21,2,8,14
	DEFB	20,26,6,12
	DEFB	18,24,4,10
	DEFB	16,22

;	Disk Parameter Block for standard 8" Floppy.

DPBLOK:
	DEFW	26			; Sectors per track
	DEFB	3			; Block shift constant
	DEFB	7			; Block mask constant
	DEFB	0			; Block extent Constatnnt
	DEFW	242			; Maximum block number
	DEFW	63			; Maximum Direct. Entry
	DEFB	11000000B		; Allocation Mask MSB
	DEFB	00000000B		;	"      "  LSB
	DEFW	16			; Check size
	DEFW	2			; Reserved tracks

;	Disk I/O Buffers for BDOS file Handler.

DIRBUF:
	DEFS	128			; Scratch Directory
ALLOC0:
	DEFS	32			; Allocation Buffer 0
CHECK0:
	DEFS	16			; Check Vector 0
ALLOC1:
	DEFS	32
CHECK1:
	DEFS	16
ALLOC2:
	DEFS	32
CHECK2:
	DEFS	16
ALLOC3:
	DEFS	32
CHECK3:
	DEFS	16

	END	COLD_BOOT
